package org.dreamwork.jasmine2.engine;

import org.apache.log4j.Logger;
import org.dreamwork.jasmine2.ajax.AjaxException;
import org.dreamwork.jasmine2.annotation.AjaxMethod;
import org.dreamwork.jasmine2.annotation.AjaxParameter;
import org.dreamwork.jasmine2.annotation.AjaxService;
import org.dreamwork.jasmine2.web.IWebContainer;
import org.dreamwork.jasmine2.web.controls.InternalJS;
import org.dreamwork.jasmine2.web.controls.Literal;
import org.dreamwork.jasmine2.web.controls.Page;
import org.dreamwork.jasmine2.web.html.GenericHtmlControl;
import org.dreamwork.util.StringUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by IntelliJ IDEA.
 * User: seth.yang
 * Date: 2010-12-10
 * Time: 14:08:39
 */
public class AjaxManager {
    private static final Logger logger = Logger.getLogger (AjaxManager.class);
    private static Map<String, Class> map;

    static void loadServiceList () throws Exception {
        ClassLoader loader = AjaxManager.class.getClassLoader ();
        Enumeration<URL> urls = loader.getResources ("META-INF/jasmine.ajax.xml");
        map = new HashMap<String, Class> ();
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
        DocumentBuilder builder = dbf.newDocumentBuilder ();
        for (; urls.hasMoreElements ();) {
            URL url = urls.nextElement ();
            InputStream in = url.openStream ();
            try {
                parseFile (builder, in);
            } finally {
                in.close ();
            }
        }
    }

    @SuppressWarnings ("unchecked")
    private static void parseFile (DocumentBuilder builder, InputStream in) throws IOException, SAXException, ClassNotFoundException {
        Document doc = builder.parse (in);
        Element root = doc.getDocumentElement ();
        NodeList list = root.getChildNodes ();
        for (int i = 0; i < list.getLength (); i++) {
            Node node = list.item (i);
            if ("class".equalsIgnoreCase (node.getNodeName ()))
                processClass (node);
            else if ("package".equalsIgnoreCase (node.getNodeName ()))
                processPackage (node);
        }
    }

    private static void processPackage (Node node) throws IOException, ClassNotFoundException {
        String packageName = node.getTextContent ();
        if (!StringUtil.isEmpty (packageName)) {
//            Package p = Package.getPackage (packageName);
            String name = packageName.replace('.', '/');
            Enumeration<URL> en = AjaxManager.class.getClassLoader ().getResources (name);
            for (; en.hasMoreElements ();) {
                URL url = en.nextElement ();
                String protocol = url.getProtocol ();
                if ("file".equalsIgnoreCase (protocol)) {
                    String fileName = url.getFile ();
                    File dir = new File (fileName);
                    File[] files = dir.listFiles ();
                    for (File file : files) {
                        String className = file.getName ();
                        className = className.substring (0, className.lastIndexOf ('.'));
                        Class c = AjaxManager.class.getClassLoader ().loadClass (packageName + '.' + className);
                        processClass (c);
                    }
                } else if ("jar".equalsIgnoreCase (protocol)) {
                    JarFile jar = ((JarURLConnection) url.openConnection ()).getJarFile ();
                    for (Enumeration<JarEntry> e = jar.entries (); e.hasMoreElements ();) {
                        String s = e.nextElement ().getName ();
                        if (s.charAt (0) == '/') s = s.substring (1);
                        if (s.startsWith (name) && s.endsWith (".class")) {
                            String className = s.substring (0, s.length () - 6).replace ('/', '.');
                            Class c = AjaxManager.class.getClassLoader ().loadClass (className);
                            processClass (c);
                        }
                    }
                }
            }
        }
    }


    private static void processClass (Node node) throws ClassNotFoundException {
        String className = node.getTextContent ();
        if (!StringUtil.isEmpty (className)) {
            processClass (Class.forName (className));
        }
    }

    @SuppressWarnings ("unchecked")
    private static void processClass (Class c) {
        AjaxService as = (AjaxService) c.getAnnotation (AjaxService.class);
        if (as == null) {
            logger.warn ("class " + c.getCanonicalName () + " is not an AjaxService. Ignore it.");
            return;
        }
        map.put (as.value (), c);
    }

    public static Class get (String name) {
        return map.get (name);
    }

    public static void registerAjaxService (Page page, String name, String methodName) {
        Class clazz = map.get (name);
        if (clazz == null)
            throw new AjaxException ("Ajax Service: " + name + " not found!");

        page.registerClientScriptBlock (InternalJS.class, InternalJS.UTIL_JS);
        page.registerClientScriptBlock (InternalJS.class, InternalJS.JASMINE_AJAX_JS);
        GenericHtmlControl script = new GenericHtmlControl ("script");
        script.setAttribute ("language", "javascript");
        script.setAttribute ("type", "text/javascript");
        script.setAttribute ("charset", "UTF-8");
        StringBuilder builder = new StringBuilder ();
        builder.append ("var ").append (name).append (" = {\r\n");
        String[] a = methodName.split (",|\\s");
        Method[] ms = clazz.getMethods ();
        int index = 0;
        for (Method m : ms) {
            if (StringUtil.inArray (a, m.getName ())) {
                if (index != 0) builder.append (",\r\n");
                AjaxMethod am = m.getAnnotation (AjaxMethod.class);
                if (am == null || !am.support ())
                    throw new AjaxException ("Method: " + m.getName () + " is not an Ajax method!");
                builder.append (m.getName ()).append (" : function (");
                Annotation[][] aps = m.getParameterAnnotations ();
                for (Annotation[] ap : aps)
                    builder.append (((AjaxParameter) ap[0]).name ()).append (", ");
                builder.append ("callback) {\r\n");
                if (aps.length == 0)
                    builder.append ("var param = ''");
                else {
                    builder.append ("var param = ");
                    for (int i = 0; i < aps.length; i++) {
                        String n = ((AjaxParameter) aps[i][0]).name ();
                        if (i != 0) builder.append (" + ");
                        builder.append ("'");
                        if (i != 0) builder.append ('&');
                        builder.append (n).append ("=' + encodeURIComponent(").append (n).append (')');
                    }
                }
                builder.append (";\r\nJasmineAjax.send ('").append (name).append ('/').append (m.getName ()).append (".ajax', '")
                        .append (am.method ()).append ("', param, callback);\r\n");
                builder.append ('}');
                index++;
            }
        }

        builder.append ("\r\n}");
//        System.out.println (builder);
        script.addControl (new Literal (builder));
        IWebContainer c = page.getHead ();
        if (c == null) c = page.getBody ();
        if (c == null) c = page.getForm ();
        if (c == null) c = page.getHtml ();
        if (c == null) c = page;
        c.addControl (script);
    }

/*
    public static void main (String[] args) throws Exception {
        loadServiceList ();
        registerAjaxService ("test", "ajaxService1,ajaxService2");
    }
*/
}